﻿// author : David Janacek, dj-soft@seznam.cz, 2013-12-13
// company: Asseco Solution, a.s. CZ
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Noris.LCS.AIDCCodes.Common;

namespace Noris.LCS.AIDCCodes.BarCodes
{
    #region abstract class BarEncoderCommon : Ancestor for any BarCode encoders.
    /// <summary>
    /// Ancestor for specific BarCode encoders.
    /// </summary>
    internal abstract class BarEncoderCommon : IEncoderEngine
    {
        #region IEncoderEngine Members
        IEnumerable<CodeType> IEncoderEngine.EncoderForTypes { get { return this.EncoderForTypes; } }
        CodeLabel IEncoderEngine.Label { get { return this.CodeLabel; } }
        CodeMatrix IEncoderEngine.Matrix { get { return this.Matrix; } }
        void IEncoderEngine.Encode(string content, CodeType codeType)
        {
            this._Reset();
            this.CheckContent(content, codeType);
            this.Content = content;
            this.Encode(content, codeType);
        }
        /// <summary>
        /// Private Reset
        /// </summary>
        private void _Reset()
        {
            this.CodeLabelClear();
            this._Buffer = null;
            this.Content = null;
            this.Matrix = null;
            this.Reset();
        }
        /// <summary>
        /// Reset of instance, is called before new encoding begins.
        /// Instance of encoder is used repeatedly to many encoding!
        /// Does not clear permanently existing CodeTable, but must clear working tables and variables.
        /// </summary>
        protected virtual void Reset() { }
        /// <summary>
        /// Check content, if is OK for current code type
        /// </summary>
        /// <param name="content"></param>
        /// <param name="codeType"></param>
        protected virtual void CheckContent(string content, CodeType codeType) { }
        /// <summary>
        /// Perform encoding of content and code type
        /// </summary>
        /// <param name="content"></param>
        /// <param name="codeType"></param>
        protected abstract void Encode(string content, CodeType codeType);
        /// <summary>
        /// Return a list of CodeType, for which is current encoder valid.
        /// </summary>
        protected abstract IEnumerable<CodeType> EncoderForTypes { get; }
        /// <summary>
        /// Content encoded in matrix
        /// </summary>
        protected string Content { get; set; }
        /// <summary>
        /// Text label for code (+ lead char, - control chars...)
        /// </summary>
        protected string xxxLabel { get; set; }
        /// <summary>
        /// Matrix of code
        /// </summary>
        protected CodeMatrix Matrix { get; set; }
        /// <summary>
        /// Namespace.Name of current type
        /// </summary>
        protected string EncoderType { get { Type type = this.GetType(); return type.Namespace + "." + type.Name; } }
        #endregion
        #region CodeTable
        /// <summary>
        /// CodeTable: for text (character) contain a code.
        /// Autoinitializing property.
        /// Creating content of CodeTable is in abstract method CreateCodeTable(), called when necessary.
        /// </summary>
        protected Dictionary<string, string> CodeTable
        {
            get
            {
                if (this._CodeTable == null)
                    this._CodeTable = this.CreateCodeTable();
                if (this._CodeTable == null)
                    throw new InvalidProgramException("BarCode encoder " + this.GetType().Namespace + "." + this.GetType().Name + " does not prepared CodeTable (in method CreateCodeTable()).");
                return this._CodeTable;
            }
        }
        private Dictionary<string, string> _CodeTable = null;
        /// <summary>
        /// Create and return CodeTable.
        /// This method is called when necessary (in this.get_CodeTable()), not from constructor.
        /// </summary>
        /// <returns></returns>
        protected abstract Dictionary<string, string> CreateCodeTable();
        /// <summary>
        /// "[" = Code for Start pattern
        /// </summary>
        protected const string CODE_START = "[";
        /// <summary>
        /// "]" = Code for Stop pattern
        /// </summary>
        protected const string CODE_STOP = "]";
        #endregion
        #region Buffer, CodeLabel
        /// <summary>
        /// Buffer containg created code (sequence of strings)
        /// </summary>
        protected StringBuilder Buffer
        {
            get
            {
                if (this._Buffer == null)
                    this._Buffer = new StringBuilder();
                return this._Buffer;
            }
        }
        private StringBuilder _Buffer;
        /// <summary>
        /// Search CodeTable for code, and add its value to Buffer.
        /// </summary>
        /// <param name="code"></param>
        protected void AddCodeToBuffer(string code)
        {
            string text;
            if (!this.CodeTable.TryGetValue(code, out text))
                throw new InvalidProgramException("Encoder " + this.EncoderType + " failed to encode content = " + this.Content + "; CodeTable does not contain text for code = " + code);
            this.AddTextToBuffer(text);
        }
        /// <summary>
        /// Add a text into Buffer.
        /// </summary>
        /// <param name="text"></param>
        protected void AddTextToBuffer(string text)
        {
            if (text != null)
            {
                this.CodeLabelLastPositionX = this.Buffer.Length;
                this.CodeLabelLastCodeWidth = text.Length;
                this.Buffer.Append(text);
            }
        }
        /// <summary>
        /// Clear a CodeLabel
        /// </summary>
        protected void CodeLabelClear()
        {
            this._CodeLabel = null;
            this.CodeLabelLastPositionX = null;
            this.CodeLabelLastCodeWidth = null;
        }
        /// <summary>
        /// Add character to CodeLabel, at specified position
        /// </summary>
        /// <param name="text"></param>
        /// <param name="y"></param>
        /// <param name="height"></param>
        protected void AddCharToLabel(char text, int x, int width, int y, int height)
        {
            this.CodeLabel.Add(text, new System.Drawing.Rectangle(x, y, width, height));
        }
        /// <summary>
        /// Add character to CodeLabel, at X position by last inserted code, at Y position.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="y"></param>
        /// <param name="height"></param>
        protected void AddCharToLabel(char text, int y, int height)
        {
            if (!(this.CodeLabelLastPositionX.HasValue && this.CodeLabelLastCodeWidth.HasValue))
                throw new InvalidProgramException(this.EncoderType + ": AddCharToLabel() was called without previous calling of AddCodeToBuffer() or AddTextToBuffer() methods; CodeLabelLast is empty.");

            this.CodeLabel.Add(text, new System.Drawing.Rectangle(this.CodeLabelLastPositionX.Value, y, this.CodeLabelLastCodeWidth.Value, height));
        }
        /// <summary>
        /// Store text to CodeLabel
        /// </summary>
        /// <param name="content"></param>
        /// <param name="y"></param>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <param name="height"></param>
        protected void AddTextToLabel(string content, int y, int left, int right, int height)
        {
            this.CodeLabelClear();
            int len = content.Length;
            if (len == 0) return;

            int width = this.Buffer.Length - (left + right);
            int w = width / len;
            int wSum = w * len;
            int x = left + ((width - wSum) / 2);
            foreach (char c in content)
            {
                this.AddCharToLabel(c, x, w, y, height);
                x += w;
            }
        }
        /// <summary>
        /// Store text to CodeLabel
        /// </summary>
        /// <param name="content"></param>
        /// <param name="y"></param>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <param name="height"></param>
        protected void AddTextToLabel(string content, int y, int w, int height)
        {
            this.CodeLabelClear();
            int len = content.Length;
            if (len == 0) return;

            int width = this.Buffer.Length;
            int wSum = w * len;
            if (wSum > width)
            {
                w = width / len;
                wSum = w * len;
            }
            int x = ((width - wSum) / 2);
            foreach (char c in content)
            {
                this.AddCharToLabel(c, x, w, y, height);
                x += w;
            }
        }
        /// <summary>
        /// Position X, where begins last added text to buffer (Buffer.Length before calling methods AddCodeToBuffer() or AddTextToBuffer()) = X for CodeLabelItem
        /// </summary>
        protected Int32? CodeLabelLastPositionX { get; set; }
        /// <summary>
        /// Width of a code inserted into Buffer by last called of methods AddCodeToBuffer() or AddTextToBuffer() = width for CodeLabelItem
        /// </summary>
        protected Int32? CodeLabelLastCodeWidth { get; set; }
        /// <summary>
        /// CodeLabel
        /// </summary>
        protected CodeLabel CodeLabel
        {
            get
            {
                if (this._CodeLabel == null)
                    this._CodeLabel = new Common.CodeLabel();
                return this._CodeLabel;
            }
        }
        private CodeLabel _CodeLabel;
        #endregion
        #region Create Matrix from Buffer
        /// <summary>
        /// Create Matrix for current code in Buffer.
        /// Matrix has width of buffer length, height by parameter, border = 0, and contain data from buffer ('0' / '1').
        /// </summary>
        /// <param name="height"></param>
        protected void CreateMatrixFromBuffer(string label, CodeType codeType, int height)
        {
            this.CreateMatrixFromBuffer(label, codeType, height, 0, 0, 0, 0, null);
        }
        /// <summary>
        /// Create Matrix for current code in Buffer.
        /// Matrix has width of buffer length, height and border by parameter, and contain data from buffer ('0' / '1').
        /// </summary>
        /// <param name="height"></param>
        protected void CreateMatrixFromBuffer(string label, CodeType codeType, int height, int borderL, int borderT, int borderR, int borderB)
        {
            this.CreateMatrixFromBuffer(label, codeType, height, borderL, borderT, borderR, borderB, null);
        }
        /// <summary>
        /// Create Matrix for current code in Buffer.
        /// Matrix has width of buffer length, height and border by parameter, and contain data from buffer ('0' / '1') converted by converter.
        /// Converter is method with parameters: (int x, int y, char c), x is position of char in buffer (from 0), y is position in Y axis (0 = top, max = (height - 1).
        /// Char c is character at position [x] in this.Buffer.
        /// </summary>
        /// <param name="height"></param>
        /// <param name="borderX"></param>
        /// <param name="borderY"></param>
        /// <param name="convertor"></param>
        protected void CreateMatrixFromBuffer(string label, CodeType codeType, int height, int borderL, int borderT, int borderR, int borderB, Func<int, int, char, char> convertor)
        {
            // Matrix from buffer:
            char[] content = this.Buffer.ToString().ToCharArray();
            int len = content.Length;
            int w = borderL + len + borderR;
            int h = borderT + height + borderB;
            CodeMatrix matrix = new CodeMatrix(w, h);
            for (int i = 0; i < len; i++)
                for (int l = 0; l < height; l++)
                {
                    char c = content[i];
                    char d = (convertor == null ? c : convertor(i, l, c));
                    matrix[borderL + i, borderT + l] = d;
                }

            matrix.Content = label;
            matrix.CodeType = Constants.BAR_CODE;
            matrix.CodeVersion = codeType.ToString();

            // Add CodeLabel:
            if (this._CodeLabel != null)
                matrix.CodeLabelAddRange(this._CodeLabel, borderL, borderT);

            this.Matrix = matrix;
        }
        /// <summary>
        /// Convertor, which convert pattern '0', '1' and '2' (from buffer) to point '0' and '1' by Y coordinate of point.
        /// In other words: Pattern for Start, Separator and Stop lines (char = '2') are full height; 
        /// but Data pattern (char = '1') are little shorters.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="c"></param>
        /// <returns></returns>
        protected char CodeCharConvertor(int x, int y, int dataHeight, char c)
        {
            if (c == '0') return '0';                                // White
            if (c == '1') return (y < dataHeight ? '1' : '0');       // Data lines: only specified point height (for y >= dataHeight return '0' = white)
            if (c == '2') return '1';                                // Separator = long lines: all height of 30 point = '1'
            return '0';
        }
        #endregion
        #region Services: ContainOnly(), ContainAnyOf(), ...
        /// <summary>
        /// Return true, if "content" contains only enabled characters (enabledChars).
        /// Return false, if content or enabledChars is empty.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="enabledChars"></param>
        /// <returns></returns>
        protected bool ContainOnly(string content, string enabledChars)
        {
            if (String.IsNullOrEmpty(content)) return false;
            if (String.IsNullOrEmpty(enabledChars)) return false;
            foreach (char c in content)
            {
                if (enabledChars.IndexOf(c) < 0)
                    return false;
            }
            return true;
        }
        /// <summary>
        /// Return true, if "content" contains only enabled characters (enabledChars).
        /// Return false, if content or enabledChars is empty.
        /// </summary>
        /// <param name="c"></param>
        /// <param name="enabledChars"></param>
        /// <returns></returns>
        protected bool ContainOnly(char c, string enabledChars)
        {
            if (enabledChars.IndexOf(c) < 0)
                return false;
            return true;
        }
        /// <summary>
        /// Return true, if "content" contains only digits (0 to 9).
        /// Return false, if content is empty.
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        protected bool ContainOnlyDigit(string content)
        {
            if (String.IsNullOrEmpty(content)) return false;
            foreach (char c in content)
            {
                if (!ContainOnlyDigit(c)) return false;
            }
            return true;
        }
        /// <summary>
        /// Return true, if "content" contains only digits (0 to 9).
        /// Return false, if content or enabledChars is empty.
        /// </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        protected bool ContainOnlyDigit(char c)
        {
            return (ToInt(c) >= 0);
        }
        /// <summary>
        /// Return true, if "content" contains any of specified characters (disabledChars).
        /// Return false, if content or enabledChars is empty.
        /// </summary>
        /// <param name="content"></param>
        /// <param name="disabledChars"></param>
        /// <returns></returns>
        protected bool ContainAnyOf(string content, string disabledChars)
        {
            if (String.IsNullOrEmpty(content)) return false;
            if (String.IsNullOrEmpty(disabledChars)) return false;
            foreach (char c in content)
            {
                if (disabledChars.IndexOf(c) >= 0)
                    return true;
            }
            return false;
        }
        /// <summary>
        /// Return true, if "content" contains only characters contained in this.CodeTable.Keys.
        /// Return false, if content is empty.
        /// </summary>
        /// <param name="content"></param>
        /// <returns></returns>
        protected bool ContainOnlyCodeTable(string content)
        {
            if (String.IsNullOrEmpty(content)) return false;
            foreach (char c in content)
            {
                if (!this.CodeTable.ContainsKey(c.ToString()))
                    return false;
            }
            return true;
        }
        /// <summary>
        /// Return check digit for code (length) by rule for EAN code (sum (digit * coefficients)), 10 - (sum % 10).
        /// </summary>
        /// <param name="code">Text content of code</param>
        /// <param name="length">Number of characters from content to calculate check digit</param>
        /// <param name="coeff0">Coefficient for multiply number from position 0,2,4,...</param>
        /// <param name="coeff1">Coefficient for multiply number from position 1,3,5,...</param>
        /// <returns></returns>
        protected string GetCheckDigitOddEven(string code, int length, int coeff0, int coeff1)
        {
            int len = code.Length;
            if (len < length)
                throw new InvalidProgramException(this.EncoderType + " can not calculate check digit for code \"" + code + "\", length must be " + length.ToString() + " or more.");

            int total = 0;
            for (int i = 0; i < length; i++)
            {
                int value = ((int)code[i]) - ZERO;
                total += value * ((i % 2 == 0) ? coeff0 : coeff1);
            }
            int cs = total % 10;
            cs = 10 - cs;
            if (cs == 10)
                cs = 0;
            return cs.ToString();
        }
        /// <summary>
        /// Return numeric value of one digit (char).
        /// For other values than '0' ÷ '9' return -1.
        /// </summary>
        /// <param name="digit">char '0' ÷ '9'. For other values return -1.</param>
        /// <returns></returns>
        protected int ToInt(char digit)
        {
            int n = ((int)digit) - ZERO;
            if (n < 0 || n > 9) return -1;
            return n;
        }
        /// <summary>
        /// Int32 value of char '0' (=48d)
        /// </summary>
        protected const int ZERO = ((int)'0');
        #endregion
    }
    #endregion
    #region abstract class EanBase : Ancestor for EAN code encoders.
    /// <summary>
    /// EanBase : abstract ancestor for EAN code encoders.
    /// Contain support for CodeTable creation, CheckContent method, CheckDigit calculation and CharConvertor from pattern to matrix.
    /// </summary>
    internal abstract class EanBase : BarEncoderCommon, IEncoderEngine
    {
        #region Definitions support
        /// <summary>
        /// Create and return CodeTable containing specified parts
        /// </summary>
        /// <param name="partA"></param>
        /// <param name="partB"></param>
        /// <param name="partC"></param>
        /// <param name="separator"></param>
        /// <returns></returns>
        protected Dictionary<string, string> GetEanCodeTable(bool partA, bool partB, bool partC, bool separator)
        {
            Dictionary<string, string> codeTable = new Dictionary<string, string>();
            if (partA)
            {
                codeTable.Add("0a", "0001101");
                codeTable.Add("1a", "0011001");
                codeTable.Add("2a", "0010011");
                codeTable.Add("3a", "0111101");
                codeTable.Add("4a", "0100011");
                codeTable.Add("5a", "0110001");
                codeTable.Add("6a", "0101111");
                codeTable.Add("7a", "0111011");
                codeTable.Add("8a", "0110111");
                codeTable.Add("9a", "0001011");
            }

            if (partB)
            {
                codeTable.Add("0b", "0100111");
                codeTable.Add("1b", "0110011");
                codeTable.Add("2b", "0011011");
                codeTable.Add("3b", "0100001");
                codeTable.Add("4b", "0011101");
                codeTable.Add("5b", "0111001");
                codeTable.Add("6b", "0000101");
                codeTable.Add("7b", "0010001");
                codeTable.Add("8b", "0001001");
                codeTable.Add("9b", "0010111");
            }

            if (partC)
            {
                codeTable.Add("0c", "1110010");
                codeTable.Add("1c", "1100110");
                codeTable.Add("2c", "1101100");
                codeTable.Add("3c", "1000010");
                codeTable.Add("4c", "1011100");
                codeTable.Add("5c", "1001110");
                codeTable.Add("6c", "1010000");
                codeTable.Add("7c", "1000100");
                codeTable.Add("8c", "1001000");
                codeTable.Add("9c", "1110100");
            }

            if (separator)
            {
                codeTable.Add(CODE_START, "202");                    // Start
                codeTable.Add(CODE_DIVIDER, "02020");                // Center divider
                codeTable.Add(CODE_STOP, "202");                     // Stop
            }

            return codeTable;
        }
        /// <summary>
        /// Prepare patterns to this.PatternsDict, for specified pattern names.
        /// Pattern is sequence (for example "aabbab"), by which to create the final code (combining code "a" or "b").
        /// Specific pattern is obtained by using method GetPattern().
        /// </summary>
        /// <param name="names"></param>
        protected virtual void PreparePatternTables(params string[] names)
        {
            if (names.Contains(PATT_EAN13))
                this.PatternsDict.Add(PATT_EAN13, new string[] { "aaaaaa", "aababb", "aabbab", "aabbba", "abaabb", "abbaab", "abbbaa", "ababab", "ababba", "abbaba" });
            if (names.Contains(PATT_UPCE0))
                this.PatternsDict.Add(PATT_UPCE0, new string[] { "bbbaaa", "bbabaa", "bbaaba", "bbaaab", "babbaa", "baabba", "baaabb", "bababa", "babaab", "baabab" });
            if (names.Contains(PATT_UPCE1))
                this.PatternsDict.Add(PATT_UPCE1, new string[] { "aaabbb", "aababb", "aabbab", "aabbba", "abaabb", "abbaab", "abbbaa", "ababab", "ababba", "abbaba" });
        }
        /// <summary>
        /// Return pattern (for example "aabbab") for specified pattern name (selected pattern family) 
        /// and digit ('0' ÷ '9') (number of pattern in family).
        /// </summary>
        /// <param name="patternName"></param>
        /// <param name="digit"></param>
        /// <returns></returns>
        protected virtual string GetPattern(string patternName, char digit)
        {
            string[] patterns = this.PatternsDict[patternName];
            int index = ToInt(digit);
            return patterns[index];
        }
        /// <summary>
        /// "|" = Code for Divider pattern
        /// </summary>
        protected const string CODE_DIVIDER = "|";
        /// <summary>EAN13 : name of patterns for EAN13 code</summary>
        protected const string PATT_EAN13 = "EAN13";
        /// <summary>UPCE0 : name of patterns for UPCE1 code</summary>
        protected const string PATT_UPCE0 = "UPCE0";
        /// <summary>UPCE1 : name of patterns for UPCE2 code</summary>
        protected const string PATT_UPCE1 = "UPCE1";
        /// <summary>
        /// Dictionary of patterns for EAN-type codes.
        /// Key = name of pattern, value = array string[10] of patterns for each digit (0 ÷ 9).
        /// </summary>
        protected Dictionary<string, string[]> PatternsDict
        {
            get
            {
                if (this._PatternsDict == null)
                    this._PatternsDict = new Dictionary<string, string[]>();
                return this._PatternsDict;
            }
        }
        private Dictionary<string, string[]> _PatternsDict;
        #endregion
        #region Encoding support
        protected void CheckContent(string content, string codeName, params int[] lens)
        {
            if (String.IsNullOrEmpty(content))
                throw new InvalidProgramException(codeName + " can not encode empty string.");
            if (!ContainOnly(content, "0123456789"))
                throw new InvalidProgramException(codeName + " can not encode string \"" + content + "\", contain invalid character (only numeric are supported).");
            int length = content.Length;
            if (lens.Length > 0 && !lens.Any(l => l == length))
            {
                string s = "";
                foreach (int l in lens)
                    s += "," + l.ToString();
                throw new InvalidProgramException(codeName + " can not encode string \"" + content + "\", length is " + length.ToString() + 
                    ", must be any of: " + s.Substring(1) + ".");
            }
        }
        /// <summary>
        /// Return check digit for code (length) by rule for EAN code (sum digit: odd 1x, even 3x), 10 - sum % 10
        /// </summary>
        /// <param name="code"></param>
        /// <param name="length"></param>
        /// <returns></returns>
        protected string GetCheckDigitEan(string code, int length)
        {
            return GetCheckDigitOddEven(code, length, 1, 3);
        }
        #endregion
    }
    #endregion
    #region abstract class InterleavedBase : Ancestor for Interleaved code encoders.
    /// <summary>
    /// EanBase : abstract ancestor for EAN code encoders.
    /// Contain support for CodeTable creation, CheckContent method, CheckDigit calculation and CharConvertor from pattern to matrix.
    /// </summary>
    internal abstract class InterleavedBase : BarEncoderCommon, IEncoderEngine
    {
        #region Definitions support
        /// <summary>
        /// Create and return CodeTable containing specified parts
        /// </summary>
        /// <param name="partI">Add Interleaved widths (NNWWN ... NWNWN)</param>
        /// <param name="separator">Add Separators (START, STOP)</param>
        /// <returns></returns>
        protected Dictionary<string, string> GetInterleavedCodeTable(bool partI, bool separator)
        {
            Dictionary<string, string> codeTable = new Dictionary<string, string>();
            if (partI)
            {
                codeTable.Add("0", "NNWWN");
                codeTable.Add("1", "WNNNW");
                codeTable.Add("2", "NWNNW");
                codeTable.Add("3", "WWNNN");
                codeTable.Add("4", "NNWNW");
                codeTable.Add("5", "WNWNN");
                codeTable.Add("6", "NWWNN");
                codeTable.Add("7", "NNNWW");
                codeTable.Add("8", "WNNWN");
                codeTable.Add("9", "NWNWN");
            }

            if (separator)
            {
                codeTable.Add(CODE_START, "2020");                   // Start
                codeTable.Add(CODE_STOP, "2202");                    // Stop
            }

            return codeTable;
        }
        #endregion
        #region Encoding support
        /// <summary>
        /// Create Interleaved pattern for two character (c0 and c1)
        /// </summary>
        /// <param name="c0"></param>
        /// <param name="c1"></param>
        /// <returns></returns>
        protected string EncodeInterleaved(char c0, char c1)
        {
            string result = "";

            // Pair of pattern for two character:
            string p0 = this.CodeTable[c0.ToString()];               // Pattern for digit at position [i + 0]), i.e. for "0" = "NNWWN"
            string p1 = this.CodeTable[c1.ToString()];               // Pattern for digit at position [i + 1]), i.e. for "6" = "NWWNN"
            string[] pair = new string[] { p0, p1 };

            // Interleaving two pattern, first (s=0) = Bars, second (s=1) = Spaces:
            for (int i = 0; i < 5; i++)
                for (int s = 0; s < 2; s++)
                {
                    char p = pair[s][i];         // One character from pattern [s = 0, 1], from position [i = 0 ÷ 4]
                    // If s == 0, then encoding "Bars" (i.e. text = "1"), else if s == 1, encoding "Space" (text = "0").
                    // If pattern (p) == 'N', then width = "Narrow" (text length = 1), else if p == 'W', then width = "Wide" (2):
                    string t = ((s == 0) ? (p == 'N' ? "1" : "11") : (p == 'N' ? "0" : "00"));
                    result += t;
                }

            return result;
        }
        /// <summary>
        /// Add two characters (c0, c1) to label, at positions CodeLabelLastPositionX + CodeLabelLastCodeWidth.
        /// </summary>
        /// <param name="c0"></param>
        /// <param name="c1"></param>
        /// <param name="y"></param>
        /// <param name="height"></param>
        protected void AddCharsToLabel(char c0, char c1, int y, int height)
        {
            int x = this.CodeLabelLastPositionX.Value;           // Position X of begin of current two character pattern
            int w = this.CodeLabelLastCodeWidth.Value;
            int w0 = w / 2;
            int w1 = w - w0;
            this.AddCharToLabel(c0, x, w0, y, height);
            this.AddCharToLabel(c1, x + w0, w1, y, height);
        }
        #endregion
    }
    #endregion
}
